package com.bbird.od.finance.service.domain;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.DateUtils;
import com.bbird.od.finance.service.models.BankFlow;
import com.bbird.od.finance.service.models.DeliveryFlow;
import com.bbird.od.finance.service.models.ERPFlow;
import com.bbird.od.finance.service.models.ReconciliationResult;
import com.bbird.od.finance.service.models.type.BankType;
import com.bbird.od.finance.service.models.type.FinallyReceiveType;
import com.bbird.od.finance.service.models.type.HomeOrderType;
import com.bbird.od.finance.service.models.type.YesOrNo;
import com.bbird.od.finance.service.listener.FlowExcelListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;

import java.io.File;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author ziyang Kuang
 * @date 2020/11/16
 * @describe
 */

@Slf4j
public abstract class AbstractReconciliationHandler<BANK_FLOW> implements ReconciliationService {

    protected ThreadLocal<Map<String, ReconciliationResult>> reconciliationResultsMapThreadLocal = new ThreadLocal<>();

    // 对账逻辑总共13个步骤
    private Integer totalStep = 13;
    private Integer currentProgress = 0;
    private boolean finished = false;
    private boolean success = true;

    @Override
    public BigDecimal getCurrentProgress() {
        return new BigDecimal(currentProgress).divide(new BigDecimal(totalStep), 2, BigDecimal.ROUND_HALF_UP);
    }

    @Override
    public boolean isFinished() {
        return finished;
    }

    @Override
    public boolean isSuccess() {
        return success;
    }

    @Override
    public void reconciliation(File erpFlowExcel, File bankFlowExcel, File deliveryFlowExcel, File historyExcel, String exportPath) {

        try {
            /** ======= 数据准备 ======= */
            StopWatch stopWatchTotal = StopWatch.createStarted();
            StopWatch stopWatch = StopWatch.createStarted();
            // 解析ERP数据
            List<ERPFlow> erpFlows = analyseErpFlow(erpFlowExcel);
            currentProgress++;
            log.info("[{}] 解析ERP数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 解析银行数据
            stopWatch.start();
            List<BANK_FLOW> bankFlows = analyseBankFlow(bankFlowExcel);
            currentProgress++;
            log.info("[{}] 解析银行数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 解析发货数据
            stopWatch.start();
            List<DeliveryFlow> deliveryFlows = analyseDeliveryFlow(deliveryFlowExcel);
            currentProgress++;
            log.info("[{}] 解析发货数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 解析历史对账数据
            stopWatch.start();
            List<ReconciliationResult> historyList = analyseHistory(historyExcel);
            currentProgress++;
            reconciliationResultsMapThreadLocal.set(new HashMap<>(erpFlows.size()));
            log.info("[{}] 解析历史对账数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());

            /** ======= 对账处理 ======= */
            stopWatch.reset();

            // 加载历史未核销数据
            stopWatch.start();
            loadHistory(historyList);
            currentProgress++;
            log.info("[{}] 加载历史未核销数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 正向对账（检查未收）
            stopWatch.start();
            erpToBankCheck(erpFlows, bankFlows);
            currentProgress++;
            log.info("[{}] 正向对账（检查未收）: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 反向对账（检查预收）
            stopWatch.start();
            bankToErpCheck(bankFlows);
            currentProgress++;
            log.info("[{}] 反向对账（检查预收）: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 核销
            stopWatch.start();
            writeOff();
            currentProgress++;
            log.info("[{}] 核销: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 并单处理
            stopWatch.start();
            deliveryCheck(deliveryFlows);
            currentProgress++;
            log.info("[{}] 并单处理: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 处理ERP退货情况
            stopWatch.start();
            erpRefundProcessing();
            currentProgress++;
            log.info("[{}] 处理ERP退货情况: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 处理拼单且有运费情况
            stopWatch.start();
            resultMergerWithTransportationAmount();
            currentProgress++;
            log.info("[{}] 处理拼单且有运费情况: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();

            // 本期未核销数据
            stopWatch.start();
            List<ReconciliationResult> newHistoryList = processingHistory();
            currentProgress++;
            log.info("[{}] 本期未核销数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.reset();


            stopWatch.start();
            // 导出本期对账结果数据, 生成往期对账结果数据
            export(exportPath, newHistoryList);
            currentProgress++;
            log.info("[{}] 导出本期对账结果数据, 生成往期对账结果数据: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
            stopWatch.stop();
            stopWatchTotal.stop();
            log.info("[{}] 对账总耗时: {}ms", this.getBankType().getLabel(), stopWatch.getTime());
        } catch (Exception e) {
            log.error("[{}] 对失败, 发生异常", this.getBankType().getLabel(), e);
            success = false;
        }

        log.info("[{}] 对账结束", this.getBankType().getLabel());
        finished = true;
    }

    protected void export(String filePath, List<ReconciliationResult> newHistoryList) {

        String currentDate = DateUtils.format(new Date(), "yyyyMMdd");
        String reallyFilePath = MessageFormat.format("{0}\\{1}\\{2}", filePath, currentDate, getBankType().getLabel());
        File reallyFolder = new File(reallyFilePath);
        if (!reallyFolder.exists()) {
            reallyFolder.mkdirs();
        }

        String currentResultFileName = MessageFormat.format("{0}\\本期对账结果_{1}_{2}.xlsx", reallyFilePath, getBankType().getLabel(), currentDate);
        String historyResultFileName = MessageFormat.format("{0}\\历史对账结果(未核销)_{1}_{2}.xlsx", reallyFilePath, getBankType().getLabel(), currentDate);

        List<ReconciliationResult> reconciliationResultResults = reconciliationResultsMapThreadLocal.get().entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toList());
        EasyExcel.write(currentResultFileName, ReconciliationResult.class).sheet("对账结果").doWrite(reconciliationResultResults);
        EasyExcel.write(historyResultFileName, ReconciliationResult.class).sheet("历史对账结果(未核销)").doWrite(newHistoryList);
    }

    /**
     * 核销
     */
    protected void writeOff() {
        Map<String, ReconciliationResult> reconciliationResultMap = reconciliationResultsMapThreadLocal.get();
        reconciliationResultMap.forEach((k, result) -> {
            writeOff(result);
        });
    }

    /**
     * 核销
     */
    protected boolean writeOff(ReconciliationResult result) {
        // 计算实收
        result.setReceivedAmount(this.calcReceivedAmount(result));
        // 检查是否核销
        FinallyReceiveType finallyReceiveType = getFinallyReceiveType(result);
        result.setFinallyReceiveType(finallyReceiveType);
        result.setWriteOff(finallyReceiveType == FinallyReceiveType.NORMAL ? YesOrNo.YES : YesOrNo.NO);
        // 计算核销差额
        result.setDiffReceivableAmount(result.getReceivableAmount().subtract(result.getReceivedAmount()));
        return finallyReceiveType == FinallyReceiveType.NORMAL;
    }

    /**
     * 是否核销
     * 已核销：NORMAL
     */
    protected FinallyReceiveType getFinallyReceiveType(ReconciliationResult result) {
        if (result.getReceivableAmount().compareTo(result.getReceivedAmount()) == 0) {
            // 核销
            return FinallyReceiveType.NORMAL;
        } else if (result.getReceivableAmount().compareTo(result.getReceivedAmount()) < 0) {
            // 预收
            return FinallyReceiveType.ADVANCE;
        } else if (result.getReceivableAmount().compareTo(result.getReceivedAmount()) > 0) {
            // 未收
            return FinallyReceiveType.UN_RECEIVE;
        }
        return null;
    }

    /**
     * 并单处理
     */
    protected void deliveryCheck(List<DeliveryFlow> deliveryFlows) {
        if (CollectionUtils.isEmpty(deliveryFlows)) {
            return;
        }

        /**
         * 并单处理逻辑1
         * 利用对账结果预收的订单，从发货流水中找拼单
         */
        Map<String, ReconciliationResult> resultMap = reconciliationResultsMapThreadLocal.get();
        // 预收的数据
        Map<String, ReconciliationResult> advanceMap = resultMap.entrySet().stream().map(Map.Entry::getValue)
                .filter(result -> result.getFinallyReceiveType() == FinallyReceiveType.ADVANCE)
                .collect(Collectors.toMap(k -> k.getHomeOrderNumber(), v -> v));

        // 找出预收的主订单
        List<String> mainOrderNumberList = deliveryFlows.stream().filter(deliveryFlow ->
                advanceMap.containsKey(deliveryFlow.getHomeOrderNumber())
        ).collect(Collectors.toList())
                .stream().map(DeliveryFlow::getHomeOrderGroupNum)
                .distinct().collect(Collectors.toList());

        // 找出主订单下的拼单
        Map<String, List<DeliveryFlow>> orderGroupMap = deliveryFlows.stream()
                .filter(deliveryFlow -> mainOrderNumberList.contains(deliveryFlow.getHomeOrderGroupNum()))
                .collect(Collectors.groupingBy(DeliveryFlow::getHomeOrderGroupNum));

        orderGroupMap.forEach((groupOrderNumber, deliverys) -> {
            if (deliverys.size() <= 1) {
                return;
            }
            // 主单
            ReconciliationResult groupMainResult = resultMap.get(groupOrderNumber);
            if (groupMainResult == null) {
                return;
            }

            groupMainResult.setMainHomeOrderNumber(groupOrderNumber);
            groupMainResult.setMerger(YesOrNo.YES);

            // 拼单
            deliverys.forEach(delivery -> {
                String homeOrderNumber = delivery.getHomeOrderNumber();
                // 主单不需要计算
                if (homeOrderNumber.equals(groupOrderNumber)) {
                    return;
                }
                ReconciliationResult result = resultMap.get(homeOrderNumber);
                // 排除金额为零的数据
                if (result == null && delivery.getAmount().compareTo(BigDecimal.ZERO) != 0) {
                    result = new ReconciliationResult();
                    result.setHomeOrderNumber(homeOrderNumber);
                }
                // 数据无效
                if (result == null) {
                    return;
                }
                // 设置实际应收
                // 拼单金额 = 发货金额累计
                result.setReceivableAmount(result.getReceivableAmount().add(delivery.getAmount()));
                result.setMainHomeOrderNumber(groupOrderNumber);
                result.setMerger(YesOrNo.YES);
                resultMap.put(homeOrderNumber, result);

                // 非预收情况下才需要将拼单金额逐项扣除
//                if (groupMainResult.getReceivableAmount() != BigDecimal.ZERO) {
                // 主单应收金额 = 主单应收金额 - 拼单金额
                groupMainResult.setReceivableAmount(groupMainResult.getReceivableAmount().subtract(delivery.getAmount()));
//                }
            });
        });
        // 核销
        writeOff();

        /**
         * 并单处理逻辑2
         * 从发货流水的“原始订单编号”中找出拼单
         */
        // 原始订单中的拼单(原始订单号去重)
        List<DeliveryFlow> mergeOrders = deliveryFlows.stream()
                .filter(deliveryFlow -> {
                    if (StringUtils.isBlank(deliveryFlow.getOriginalOrderNumber())) {
                        // 过滤空数据
                        return false;
                    }
                    boolean moreThanOne = deliveryFlow.getOriginalOrderNumber().split(",").length > 1;
                    boolean homeNumberSame = deliveryFlow.getHomeOrderGroupNum().equals(deliveryFlow.getHomeOrderNumber());
                    return moreThanOne && homeNumberSame;
                })
                .collect(Collectors.toMap(k -> k.getOriginalOrderNumber(), v -> v, (k1, k2) -> k1))
                .entrySet().stream()
                .map(Map.Entry::getValue).collect(Collectors.toList());
        mergeOrders.forEach(deliveryFlow -> {
            String groupHomeOrderNumber = deliveryFlow.getHomeOrderGroupNum();
            // 主订单
            ReconciliationResult groupHomeOrder = resultMap.get(groupHomeOrderNumber);
            if (groupHomeOrder == null) {
                // 找不到主订单
                return;
            }

            String[] mergeOrderNums = deliveryFlow.getOriginalOrderNumber().split(",");
            for (String mergeOrderNum : mergeOrderNums) {
                if (!groupHomeOrderNumber.equals(mergeOrderNum)) {
                    // 合并的子订单
                    ReconciliationResult mergeOrder = resultMap.get(mergeOrderNum);
                    if (mergeOrder == null) {
                        continue;
                    }
                    mergeOrder.setMerger(YesOrNo.YES);
                    mergeOrder.setMainHomeOrderNumber(groupHomeOrderNumber);
                    // 子订单应收 = 子订单实收
                    mergeOrder.setReceivableAmount(mergeOrder.getReceivedAmount());
                    // 主订单应收 = 主订单应收 - 子订单实收
                    groupHomeOrder.setReceivableAmount(groupHomeOrder.getReceivableAmount().subtract(mergeOrder.getReceivedAmount()));
                }
            }
        });
        // 核销
        writeOff();
    }

    /**
     * 解析ERP数据
     */
    protected List<ERPFlow> analyseErpFlow(File erpFlowExcel) {
        FlowExcelListener<ERPFlow> listener = new FlowExcelListener(this.getBankType(), erpFlowExcel.getName());
        EasyExcel.read(erpFlowExcel, ERPFlow.class, listener).headRowNumber(10).sheet().doRead();
        return listener.getDataList();
    }

    /**
     * 解析出货记录数据
     */
    protected List<DeliveryFlow> analyseDeliveryFlow(File deliveryFlowExcel) {
        if (deliveryFlowExcel == null) {
            return null;
        }
        FlowExcelListener<DeliveryFlow> listener = new FlowExcelListener(this.getBankType(), deliveryFlowExcel.getName());
        EasyExcel.read(deliveryFlowExcel, DeliveryFlow.class, listener).headRowNumber(1).sheet().doRead();
        return listener.getDataList();
    }

    /**
     * 解析历史未核销数据
     */
    protected List<ReconciliationResult> analyseHistory(File historyExcel) {
        if (historyExcel == null) {
            return null;
        }
        FlowExcelListener<ReconciliationResult> listener = new FlowExcelListener(this.getBankType(), historyExcel.getName());
        EasyExcel.read(historyExcel, ReconciliationResult.class, listener).headRowNumber(1).sheet().doRead();
        return listener.getDataList();
    }

    /**
     * 加载历史未核销数据
     */
    protected void loadHistory(List<ReconciliationResult> historyList) {
        if (CollectionUtils.isEmpty(historyList)) {
            return;
        }

        Map<String, ReconciliationResult> resultMap = reconciliationResultsMapThreadLocal.get();
        historyList.forEach(history -> {
            resultMap.put(history.getHomeOrderNumber(), history);
        });
    }

    /**
     * 正向对账（检查未收）
     */
    protected void erpToBankCheck(List<ERPFlow> erpFlows, List<BANK_FLOW> bankFlows) {
        Map<String, BANK_FLOW> bankFlowMap = bankFlows.stream()
                .collect(Collectors.toMap(bankFlow -> ((BankFlow) bankFlow).getHomeOrderNumber(), bankFlow -> bankFlow, (k1, k2) -> k1));
        Map<String, ReconciliationResult> reconciliationResultsMap = reconciliationResultsMapThreadLocal.get();

        erpFlows.forEach(erpFlow -> {
            String homeOrderNumber = erpFlow.getHomeOrderNumber();
            ReconciliationResult result = reconciliationResultsMap.get(homeOrderNumber);
            if (result == null) {
                result = new ReconciliationResult();
                result.setHomeOrderNumber(homeOrderNumber);
                result.setHomeOrderType(erpFlow.getHomeOrderType());
            }

            // 应收金额 = 应收增加 - 应收减少
            BigDecimal receivableAmount = erpFlow.getReceivableAddAmount().subtract(erpFlow.getReceivableReduceAmount());
            // 应收金额累计
            result.setReceivableAmount(result.getReceivableAmount().add(receivableAmount));

            // 发生退货, 设置退货款
            if (result.getHomeOrderType() == HomeOrderType.SALES_RETURN) {
                result.setRefunded(YesOrNo.YES);
                String description = erpFlow.getDescription();
                String originOrderNumber = Pattern.compile("[^a-zA-Z0-9-]").matcher(description).replaceAll("");
                result.setRefundHomeOrderNumber(originOrderNumber);
            }

            reconciliationResultsMap.put(homeOrderNumber, result);
        });
    }

    /**
     * 反向对账（检查预收）
     */
    protected void bankToErpCheck(List<BANK_FLOW> bankFlows) {
        Map<String, ReconciliationResult> reconciliationResultMap = reconciliationResultsMapThreadLocal.get();
        bankFlows.forEach(bankFlow -> {
            BankFlow bankFlowSuper = (BankFlow) bankFlow;
            String homeOrderNumber = bankFlowSuper.getHomeOrderNumber();
            ReconciliationResult result = reconciliationResultMap.get(homeOrderNumber);
            if (result == null) {
                // 预收款
                result = new ReconciliationResult();
                result.setHomeOrderNumber(homeOrderNumber);
                reconciliationResultMap.put(homeOrderNumber, result);
            }
            BigDecimal amount = bankFlowSuper.getActualAmount();
            if (amount.compareTo(BigDecimal.ZERO) < 0) {
                // 设置退货款
                result.setRefundReceivableGoodsAmount(result.getRefundReceivableGoodsAmount().add(amount));
            } else {
                // 设置收货款
                result.setActualReceivableGoodsAmount(result.getActualReceivableGoodsAmount().add(amount));
            }

            result.setReceivedAmount(result.getReceivedAmount().add(amount));
        });
    }

    /**
     * 处理ERP退货情况
     */
    protected void erpRefundProcessing() {
        Map<String, ReconciliationResult> reconciliationResultMap = reconciliationResultsMapThreadLocal.get();
        reconciliationResultMap.forEach((key, result) -> {
            if (result.getHomeOrderType() != HomeOrderType.SALES_RETURN) {
                return;
            }

            String refundOrderNumber = result.getRefundHomeOrderNumber();
            ReconciliationResult originOrder = reconciliationResultMap.get(refundOrderNumber);
            if (originOrder != null) {
                // 重新计算应收金额
                BigDecimal receivableAmount = originOrder.getReceivableAmount().add(result.getReceivableAmount());
                if (StringUtils.isNotBlank(originOrder.getRefundHomeOrderNumber())) {
                    originOrder.setRefundHomeOrderNumber(originOrder.getRefundHomeOrderNumber() + "," + result.getHomeOrderNumber());
                } else {
                    originOrder.setRefundHomeOrderNumber(result.getHomeOrderNumber());
                }
                originOrder.setReceivableAmount(receivableAmount);
                result.setReceivableAmount(BigDecimal.ZERO);
                // 核销
                writeOff(originOrder);
                writeOff(result);
            }
        });
    }

    /**
     * 处理拼单且有运费情况
     */
    protected void resultMergerWithTransportationAmount() {
        Map<String, ReconciliationResult> resultMap = reconciliationResultsMapThreadLocal.get();
        resultMap.forEach((key, result) -> {
            // 主单不用处理
            if (result.getHomeOrderNumber().equals(result.getMainHomeOrderNumber())) {
                return;
            }

            // 并单且产生了运费
            BigDecimal actualReceivableTransportationAmount = result.getActualReceivableTransportationAmount();
            if (result.getMerger() == YesOrNo.YES && actualReceivableTransportationAmount.compareTo(BigDecimal.ZERO) > 0) {
                // 应收 = 发货记录金额 + 运费
                result.setReceivableAmount(result.getReceivableAmount().add(actualReceivableTransportationAmount));
                // 主单需要减去运费
                ReconciliationResult mainResult = resultMap.get(result.getMainHomeOrderNumber());
                mainResult.setReceivableAmount(mainResult.getReceivableAmount().subtract(actualReceivableTransportationAmount));
                // 重新核销
                writeOff(result);
                writeOff(mainResult);
            }
        });
    }

    /**
     * 本期未核销数据（生成未核销历史）
     */
    protected List<ReconciliationResult> processingHistory() {
        Map<String, ReconciliationResult> resultMap = reconciliationResultsMapThreadLocal.get();
        // 本期仍未核销数据
        List<ReconciliationResult> currentResultUnWriteOffList = resultMap.entrySet().stream()
                .map(Map.Entry::getValue)
                .filter(item -> item.getWriteOff() == YesOrNo.NO)
                .collect(Collectors.toList());
        return currentResultUnWriteOffList;
    }

    /**
     * 计算实收金额
     */
    protected BigDecimal calcReceivedAmount(ReconciliationResult reconciliationResult) {
        // 实收金额 = 收货款 + 退货款
        return reconciliationResult.getActualReceivableGoodsAmount().add(reconciliationResult.getRefundReceivableGoodsAmount());
    }

    /**
     * 解析银行流水文件
     */
    abstract List<BANK_FLOW> analyseBankFlow(File bankFlowExcel);


    abstract BankType getBankType();
}
